/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016 NXP
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#include "erpc_client_tcp_transport.h"
#include <cstdio>
#include <errno.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#ifdef WINSOCK_USED
#include <WS2tcpip.h>
#define _CLOSE_SOCKET ::closesocket
#define _SD_BOTH_ SD_BOTH
#define _IS_INVALID_SOCKET_(s) (s == INVALID_SOCKET)
#pragma comment(lib,"Ws2_32.lib ")
#elif defined(__unix)
#define INVALID_SOCKET -1
#define _CLOSE_SOCKET ::close
#define _SD_BOTH_ SHUT_RDWR
#define _IS_INVALID_SOCKET_(s) (s < 0)
#include <err.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#endif
#include "raii.h"
using namespace erpc;
using namespace gdface;
// Set this to 1 to enable debug logging.
// TODO fix issue with the transport not working on Linux if debug logging is disabled.
//#define TCP_TRANSPORT_DEBUG_LOG (1)

#if TCP_TRANSPORT_DEBUG_LOG
#define TCP_DEBUG_PRINT(_fmt_, ...) printf(_fmt_, ##__VA_ARGS__)
#define TCP_DEBUG_ERR(_msg_) err(errno, _msg_)
#else
#define TCP_DEBUG_PRINT(_fmt_, ...)
#define TCP_DEBUG_ERR(_msg_)
#endif
#define TCP_LOG_ERR(_msg_)

thread_local _SOCK_TYPE_ ClientTCPTransport::m_socket = -1;
const uint32_t ClientTCPTransport::DEFAULT_TIMEOUT_MILLS = 4000;

////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

ClientTCPTransport::ClientTCPTransport()
: m_host(NULL)
, m_port(0)
, m_connTimeoutMills(DEFAULT_TIMEOUT_MILLS)
, m_sendTimeoutMills(DEFAULT_TIMEOUT_MILLS)
, m_recvTimeoutMills(DEFAULT_TIMEOUT_MILLS)
{
}

ClientTCPTransport::ClientTCPTransport(const char *host, uint16_t port)
: m_host(host)
, m_port(port)
, m_connTimeoutMills(DEFAULT_TIMEOUT_MILLS)
, m_sendTimeoutMills(DEFAULT_TIMEOUT_MILLS)
, m_recvTimeoutMills(DEFAULT_TIMEOUT_MILLS)
{
}

ClientTCPTransport::~ClientTCPTransport(void) {}

void ClientTCPTransport::configure(const char *host, uint16_t port)
{
    m_host = host;
    m_port = port;
}
void ClientTCPTransport::timeout(uint32_t connTimeoutMills,uint32_t sendTimeoutMills,uint32_t recvTimeoutMills)
{
	if(connTimeoutMills > 0 )
	{
		m_connTimeoutMills = connTimeoutMills;
	}
	if(sendTimeoutMills > 0 )
	{
		m_sendTimeoutMills = sendTimeoutMills;
	}
	if(recvTimeoutMills > 0 )
	{
		m_recvTimeoutMills = recvTimeoutMills;
	}
}
static int connect_wait (
	int sock,
	struct sockaddr * addr,
	size_t addrlen,
	struct timeval * timeout)
{
	int res, opt;

	// get socket flags
	if ((opt = fcntl (sock, F_GETFL, NULL)) < 0) {
		TCP_DEBUG_PRINT("fcntl F_GETFL failed\n");
		return -1;
	}

	// set socket non-blocking
	if (fcntl (sock, F_SETFL, opt | O_NONBLOCK) < 0) {
		TCP_DEBUG_PRINT("fcntl F_SETFL O_NONBLOCK failed\n");
		return -1;
	}

	// try to connect
	if ((res = connect (sock, addr, addrlen)) < 0) {
		if (errno == EINPROGRESS) {
			fd_set wait_set;

			// make file descriptor set with socket
			FD_ZERO (&wait_set);
			FD_SET (sock, &wait_set);

			// wait for socket to be writable; return after given timeout
			res = select (sock + 1, NULL, &wait_set, NULL, timeout);
		}
	}
	// connection was successful immediately
	else {
		res = 1;
	}

	// reset socket flags
	if (fcntl (sock, F_SETFL, opt) < 0) {
		TCP_DEBUG_PRINT("fcntl F_SETFL reset flags failed\n");
		return -1;
	}

	// an error occured in connect or select
	if (res < 0) {
		return -1;
	}
	// select timed out
	else if (res == 0) {
		errno = ETIMEDOUT;
		return 1;
	}
	// almost finished...
	else {
		socklen_t len = sizeof (opt);

		// check for errors in socket layer
		if (getsockopt (sock, SOL_SOCKET, SO_ERROR, &opt, &len) < 0) {
			TCP_DEBUG_PRINT("getsockopt SOL_SOCKET failed\n");
			return -1;
		}

		// there was an error
		if (opt) {
			errno = opt;
			return -1;
		}
	}

	return 0;
}

erpc_status_t ClientTCPTransport::open(void)
{
    if (!_IS_INVALID_SOCKET_(m_socket))
    {
        TCP_DEBUG_PRINT("socket already connected %d\n",m_socket);
        return kErpcStatus_Success;
    }
#ifdef WINSOCK_USED
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		TCP_DEBUG_ERR("failed to WSAStartup");
		return kErpcStatus_InitFailed;
	}
	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2) {
		WSACleanup();
		TCP_DEBUG_ERR("Winsocket version mismatch");
		return kErpcStatus_InitFailed;
	}
#endif
	struct hostent *host;
	if((host = gethostbyname(m_host)) == NULL)
	{
		TCP_DEBUG_PRINT("Unable get IP\n");
		return kErpcStatus_InitFailed;
	}
	TCP_DEBUG_PRINT("get IP of %s OF %s\n",m_host,inet_ntoa(*((struct in_addr *)host->h_addr)));
    struct timeval timeout = {(__time_t)(m_connTimeoutMills/1000),(__suseconds_t)((m_connTimeoutMills % 1000 ) * 1000)};
    struct sockaddr_in address;  /* the libc network address data structure */
    _SOCK_TYPE_ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (_IS_INVALID_SOCKET_(sock))
    {
        TCP_DEBUG_ERR("failed to create client socket");
        return kErpcStatus_InitFailed;
    }
    address.sin_addr.s_addr = ((struct in_addr *)host->h_addr)->s_addr; /* assign the address */
    address.sin_port = htons(m_port);            /* translate int2port num */
    address.sin_family = AF_INET;
    auto result = connect_wait(sock,(sockaddr*)&address,sizeof(address),&timeout);
    // Check if we were able to open a connection.
    if (result != 0)
    {
        TCP_DEBUG_PRINT("connecting failed %s\n",strerror(errno));
        _CLOSE_SOCKET(sock);
        return kErpcStatus_ConnectionFailure;
    }
    timeout = {(__time_t)(m_recvTimeoutMills / 1000),(__suseconds_t)((m_recvTimeoutMills % 1000 ) * 1000)};

	socklen_t timeout_len = sizeof(timeout);
    // receive timeout
    result = setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,timeout_len);
    if (result < 0)
	{
    	TCP_LOG_ERR("RECEIVE setsockopt SO_RCVTIMEO failed");
	}
    timeout = {(__time_t)(m_sendTimeoutMills / 1000),(__suseconds_t)((m_sendTimeoutMills % 1000) * 1000)};
	// send timeout
	result = setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,timeout_len);
	if (result < 0)
	{
		TCP_LOG_ERR("SEND setsockopt SO_SNDTIMEO failed");
	}
// On some systems (BSD) we can disable SIGPIPE on the socket. For others (Linux), we have to
// ignore SIGPIPE.
#ifdef WINSOCK_USED
	// DO NOTHING
#elif defined(SO_NOSIGPIPE)
    // Disable SIGPIPE for this socket. This will cause write() to return an EPIPE error if the
    // other side has disappeared instead of our process receiving a SIGPIPE.
    int set = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)) < 0)
    {
		_CLOSE_SOCKET(sock);
		TCP_LOG_ERR("setsockopt failed");
        return kErpcStatus_Fail;
    }
#else
    // globally disable the SIGPIPE signal
    signal(SIGPIPE, SIG_IGN);
#endif // defined(SO_NOSIGPIPE)
    m_socket = sock;

    return kErpcStatus_Success;
}

erpc_status_t ClientTCPTransport::close(void)
{
    if (!_IS_INVALID_SOCKET_(m_socket))
    {
		TCP_DEBUG_PRINT("client socket %d closed\n",m_socket);
		_CLOSE_SOCKET(m_socket);
        m_socket = INVALID_SOCKET;
    }

    return kErpcStatus_Success;
}

static thread_local int underlyingReceive_count = 0;
erpc_status_t ClientTCPTransport::underlyingReceive(uint8_t *data, uint32_t size)
{
	gdface::raii raii_socket(
			[&]() {
		// 当前方法被调用２次后client端接收结束关闭client socket
		if(!_IS_INVALID_SOCKET_(m_socket)){
			if( (underlyingReceive_count > 0)){
				close();
				underlyingReceive_count = 0;
			}else{
				underlyingReceive_count ++;
			}
		}
	});
	if( m_socket <= 0){
		underlyingReceive_count = 1;
		return kErpcStatus_ConnectionFailure;
	}

#ifdef WINSOCK_USED
	decltype(recv(0, nullptr, 0, 0)) length = 0;
#else
	decltype(read(0, nullptr, 0)) length = 0;
#endif
    // Loop until all requested data is received.
    while (size)
    {
#ifdef WINSOCK_USED
		length = recv(m_socket, (char*)data, (int)size, 0);
#else
		length = read(m_socket, data, size);
#endif
        if (length < 0)
        {
        	underlyingReceive_count = 1;
            return kErpcStatus_ReceiveFailed;
        }
        else
        {
            size -= length;
            data += length;
        }
    }

    return kErpcStatus_Success;
}

erpc_status_t ClientTCPTransport::underlyingSend(const uint8_t *data, uint32_t size)
{
    if (m_socket <= 0)
    {
    	// 发送时创建socket
    	auto status = open();
    	if(kErpcStatus_Success != status)
    	{
    		return status;
    	}
    	TCP_DEBUG_PRINT("client socket %d open\n",m_socket);
    }

    // Loop until all data is sent.
    while (size)
    {
#ifdef WINSOCK_USED
		auto result = recv(m_socket, (char*)data, (int)size, 0);
#else
		auto result = write(m_socket, data, size);
#endif  
        if (result >= 0)
        {
            size -= result;
            data += result;
        }
        else
        {
            if (errno == EPIPE)
            {
                // Server closed.
                close();
                return kErpcStatus_ConnectionClosed;
            }
            return kErpcStatus_SendFailed;
        }
    }

    return kErpcStatus_Success;
}


